package withWaiters;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.*;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;



public class Waiter implements Runnable{
	
	private boolean haveL,dirtyL,haveR,dirtyR;
	private int left,right; //this are the neighbors
	private int id;
	
	private static final int NEEDR  = 11;
	private static final int NEEDL  = 12;
	private static final int PASSR  = 13;
	private static final int PASSL  = 14;
	
	private static final int HUNGRY   = 21;
	private static final int EAT 	  = 22;
	private static final int RELFORKS = 23;
	
	static Logger logger = Logger.getLogger(Waiter.class);
	
	private int timeStamp = 0; //TODO: timeStamps!
		
	public Waiter(int philoID) {
		super();
		this.id = philoID;
		this.left  = (philoID - 1) % 5;	
		this.right = (philoID + 1) % 5;		
		switch (philoID) {
			case 0: this.haveL=true;  this.dirtyL=true;  this.haveR=true; this.dirtyR=true; break;
			case 1: this.haveL=false; this.dirtyL=false; this.haveR=true; this.dirtyR=true; break;
			case 2: this.haveL=false;  this.dirtyL=false;  this.haveR=true; this.dirtyR=true; break;
			case 3: this.haveL=false;  this.dirtyL=false;  this.haveR=true; this.dirtyR=true; break;
			case 4: this.haveL=false;  this.dirtyL=false;  this.haveR=false; this.dirtyR=false; break;
		}
		
		BasicConfigurator.configure(); 
        logger.setLevel(Level.INFO);  
	}
	
	private void sendWaiter(int waiterID, int function){
		try {
			Socket sendWaiterClientSocket = new Socket("localhost",6000 + waiterID);
			DataOutputStream out = new DataOutputStream(sendWaiterClientSocket.getOutputStream());
			out.writeInt(function);
			this.timeStamp++;
			out.writeInt(this.timeStamp);
			out.close();
			sendWaiterClientSocket.close();
			logger.info("waiter: " + this.id + "wants " + function + " from: " + waiterID);
		} catch (UnknownHostException e) {
			logger.error(e.getMessage());			
		} catch (IOException e) {
			logger.error(e.getMessage());
		}		
	}
		
	private void treatHungry(){
		if(!this.haveR){
			sendWaiter(right,NEEDL);
		}
		if(!this.haveL){
			sendWaiter(left,NEEDR);
		}
		//esperar ate que eu tenha os dois garfos no laço abaixo
		while((!this.haveR)||(!this.haveL)){
			try {
				ServerSocket hungryServerSocket = new ServerSocket(6000+this.id);
				Socket hungryClient = hungryServerSocket.accept();
				DataInputStream hungryIn = new DataInputStream(hungryClient.getInputStream());
				int hungryMessage = hungryIn.readInt();
				this.timeStamp = Math.max(this.timeStamp, hungryIn.readInt());
				hungryIn.close();
				hungryClient.close();
				hungryServerSocket.close();
				//System.out.println("");
				switch(hungryMessage){
					case PASSR:
						this.haveR = true; this.dirtyR = false;
						break;
					case PASSL:
						this.haveL = true; this.dirtyL = false;
						break;
					case NEEDR: //TODO: check what de ST notation means!
						this.haveR = false; this.dirtyR = false;
						sendWaiter(right,PASSL);
						sendWaiter(right,NEEDL);
						break;
					case NEEDL:
						this.haveL = false; this.dirtyL = false;
						sendWaiter(left,PASSR);
						sendWaiter(left,NEEDR);
						break;
				}
			} catch (IOException e) {
				logger.error("While hungry :" + e.getMessage());
			}	
			
		}
		
		
	}
	
	@Override
	public void run() {
		int mainMessage;
		//int mainTimeStamp;
		
		while(true){
			try {
				ServerSocket mainServerSocket = new ServerSocket(6000+this.id);
				Socket mainClientSocket = mainServerSocket.accept();
				DataInputStream in = new DataInputStream(mainClientSocket.getInputStream());
				mainMessage = in.readInt();
				this.timeStamp = Math.max(this.timeStamp, in.readInt());
				in.close();
				mainClientSocket.close();
				mainServerSocket.close();	
				switch(mainMessage){
					case HUNGRY:
						treatHungry();
						sendEatToPhilosopher();
						this.dirtyR = true; this.dirtyL=true;
						receiveReleaseForks();					
						break;
					case NEEDR:
						this.haveR = false; this.dirtyR = false;
						sendWaiter(right,PASSL);
						break;
					case NEEDL:
						this.haveL = false; this.dirtyL = false;
						sendWaiter(left,PASSR);
						break;				
				}
			
			} catch (IOException e) {
				logger.error("while main: " + e.getMessage());
			}
		}		
	}

	
	private void receiveReleaseForks() {
		//espera a liberacao dos garfos pelo filosofo!
		try {			
			ServerSocket releaseForksServerSocket = new ServerSocket(6800 + this.id); //TODO: continuei com o numero de portas alterado, mas tenho que checar isso
			Socket releaseForksClientSocket = releaseForksServerSocket.accept();
			DataInputStream in = new DataInputStream(releaseForksClientSocket.getInputStream());
			int releaseForksMessage = in.readInt();
			//int releaseForksTimeStamp = in.readInt();
			this.timeStamp = Math.max(this.timeStamp, in.readInt());
			System.out.println("releaseForks: " + releaseForksMessage + " timestamp: " + this.timeStamp);
			in.close();
			releaseForksClientSocket.close();
			releaseForksServerSocket.close();
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		
	}

	private void sendEatToPhilosopher() {
		try {
			Socket canEatClientSocket = new Socket("localhost",6800 + this.id);
			DataOutputStream out = new DataOutputStream(canEatClientSocket.getOutputStream());
			out.writeInt(EAT);
			this.timeStamp++;
			out.writeInt(this.timeStamp);
			out.close();
			canEatClientSocket.close();
		} catch (UnknownHostException e) {
			logger.error(e.getMessage());
		} catch (IOException e) {
			logger.error(e.getMessage());
		}
		
	}

}
